package com.fling.screen;

import java.util.ArrayList;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.ImageButton;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.fling.Constants;
import com.fling.FlingHero;
import com.fling.Locator;
import com.fling.background.BackgroundBlockManager;
import com.fling.entity.Entity;
import com.fling.game.GameInstance;
import com.fling.input.CollisionHandler;
import com.fling.input.InputHandler;
import com.fling.observer.Event;
import com.fling.observer.Observer;
import com.fling.observer.Subject;
import com.fling.tutorial.TutorialManager;

public class GameScreen implements Screen, Constants, Observer {

	private Box2DDebugRenderer renderer;
	private OrthographicCamera camera;
	private BitmapFont font, fontSmall;
	private SpriteBatch gameSprites;
	private SpriteBatch uiBatch;
	private ShapeRenderer energy;
	private ShapeRenderer energyOutline;
	private ImageButton pauseButton;
	private Stage stage;
	private Table table;
	private final ArrayList<TransitionEffect> effects;
	private GameInstance game;
	private boolean pause;
	private Subject subject;
	private boolean firstJump, secondJump, thirdJump, restart;
	private float restartTimer;
	private int distance;
	private Rectangle viewport;
	private PauseScreen pauseScreen;
	private BackgroundManager background;
	private float musicTimer;
	private TutorialManager tutorial;
	private Image coinImage;
	private boolean started;
	private BackgroundBlockManager backgroundManager;

	public static float COUTDOWN = 2.70F;
	public static float ENERGY_Y_POS = 0.98f;
	public static float ENERGY_X_POS = 0.03f;
	public static float ENERGY_X_POS_SPACING = 0.05f;

	public GameScreen(OrthographicCamera camera, FlingHero flingGame) {
		uiBatch = new SpriteBatch();
		subject = new Subject();
		subject.addObserver(Locator.getAchievements());
		subject.addObserver(this);
		subject.addObserver(Locator.getAudio());
		firstJump = secondJump = thirdJump = false;
		distance = 0;
		started = false;
		musicTimer = 0;
		restartTimer = COUTDOWN;
		restart = true;
		this.camera = camera;
		pause = false;
		gameSprites = new SpriteBatch();
		

		effects = new ArrayList<TransitionEffect>();
		// fonts
		FileHandle fontFile = Gdx.files.internal(FONT);
		FreeTypeFontGenerator generator = new FreeTypeFontGenerator(fontFile);
		FreeTypeFontParameter param = new FreeTypeFontParameter();

		param.size = (int) (50 / UI_SCALE);
		fontSmall = generator.generateFont(param);

		param.size = (int) (80 / UI_SCALE);
		font = generator.generateFont(param);
		generator.dispose();

		// table and stage ui
		createUI();
		pauseScreen = new PauseScreen(flingGame, this, subject);

		// ui
		energy = new ShapeRenderer();
		energyOutline = new ShapeRenderer();

		// game
		resize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());

		renderer = new Box2DDebugRenderer();
		CollisionHandler ch = new CollisionHandler();
		Locator.getWorld().setContactListener(ch);
		game = new GameInstance(0, subject);
		tutorial = new TutorialManager(subject);
		background = new BackgroundManager();
		backgroundManager = new BackgroundBlockManager(true);
		// input
		GestureDetector gd = new GestureDetector(new InputHandler(game.getHero(), this));
		Locator.getInputMultiplexer().addProcessor(gd);
		Locator.getInputMultiplexer().addProcessor(stage);
	}

	/**
	 * Used when menu screen selects a difficulty
	 */
	public void init() {
		game.init();
		background.switchDifficulty();
	}

	/**
	 * Renders background, then game sprites, then coin image, then energy
	 * bubbles, pause button then text
	 */
	@Override
	public void render(float delta) {
		camera.update();
		Gdx.gl.glClearColor(0.5098f, 0.4706f, 0.8392f, 1);
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
		uiBatch.begin();
		background.render(uiBatch);
		uiBatch.end();
		// renderer.render(Locator.getWorld(), camera.combined);
		gameSprites.setProjectionMatrix(camera.combined);

		// render background falling blocks and all game sprites
		gameSprites.begin();
		backgroundManager.render(gameSprites);
		game.render(gameSprites);
		gameSprites.end();

		uiBatch.begin();
		// renders coin image at top of screen
		if (Locator.difficulty == Locator.HARD) {
			coinImage.draw(uiBatch, 1);
		}
		// renders text at top of screen
		renderGUI(delta);
		uiBatch.end();
		if (!tutorial.isCompleted()) {
			tutorial.render(uiBatch);
		}
		// render energy bubbles at top of screen
		renderEnergy();

		// update all entities
		if (!pause) {
			renderCountdown(delta);
			if (!restart) {
				update(delta);
				Locator.getWorld().step(1 / 60f, 8, 4);
			}
			stage.act(delta);
			stage.draw();
		} else {
			pauseScreen.render(delta);
		}

	}

	/**
	 * Update all entities, music, background
	 * 
	 * @param delta
	 */
	public void update(float delta) {
		// update game entities
		backgroundManager.update(delta);
		game.update(delta);
		background.update(delta);

		// if the music has stopped, transition to next music in 2 seconds
		if (!Locator.getAudio().getMusic().isPlaying() && !Locator.isMute()) {
			musicTimer += delta;
			if (musicTimer > 2) {
				musicTimer = 0;
				Locator.getAudio().pickMusic();
			}
		}
		// update tutorial if applicable
		if (!tutorial.isCompleted()) {
			tutorial.update(delta);
		}
	}

	/**
	 * Render text at top of screen Coin count, Distance count, Top distance
	 * count
	 * 
	 * @param delta
	 */
	private void renderGUI(float delta) {
		if (Locator.difficulty == Locator.HARD) {
			fontSmall.draw(uiBatch, "" + Locator.getCoins(), Gdx.graphics.getWidth() * 0.28f, Gdx.graphics.getHeight() * 0.99f);
		}
		fontSmall.draw(uiBatch, "Distance: " + distance, Gdx.graphics.getWidth() * 0.40f, Gdx.graphics.getHeight() * 0.99f);
		fontSmall.draw(uiBatch, "Top: " + Locator.getTopDistance(), Gdx.graphics.getWidth() * 0.70f, Gdx.graphics.getHeight() * 0.99f);
	}

	/**
	 * Render energy bubbles for flings
	 */
	public void renderEnergy() {
		energy.begin(ShapeType.Filled);
		if (!thirdJump) {
			energy.circle(Gdx.graphics.getWidth() * ENERGY_X_POS, Gdx.graphics.getHeight() * ENERGY_Y_POS, 6 * 2 / UI_SCALE);
		}
		if (!secondJump) {
			energy.circle(Gdx.graphics.getWidth() * (ENERGY_X_POS + ENERGY_X_POS_SPACING), Gdx.graphics.getHeight() * ENERGY_Y_POS, 6 * 2 / UI_SCALE);
		}
		if (!firstJump) {
			energy.circle(Gdx.graphics.getWidth() * (ENERGY_X_POS + ENERGY_X_POS_SPACING * 2), Gdx.graphics.getHeight() * ENERGY_Y_POS,
					6 * 2 / UI_SCALE);
		}
		energy.end();
		energyOutline.begin(ShapeType.Line);
		energyOutline.circle(Gdx.graphics.getWidth() * ENERGY_X_POS, Gdx.graphics.getHeight() * ENERGY_Y_POS, 6 * 2 / UI_SCALE);
		energyOutline.circle(Gdx.graphics.getWidth() * (ENERGY_X_POS + ENERGY_X_POS_SPACING), Gdx.graphics.getHeight() * ENERGY_Y_POS,
				6 * 2 / UI_SCALE);
		energyOutline.circle(Gdx.graphics.getWidth() * (ENERGY_X_POS + ENERGY_X_POS_SPACING * 2), Gdx.graphics.getHeight() * ENERGY_Y_POS,
				6 * 2 / UI_SCALE);
		energyOutline.end();
	}

	/**
	 * Renders the "Ready, Fling!" text when the game is restarting
	 * 
	 * @param delta
	 */
	public void renderCountdown(float delta) {
		// provide a countdown every time game starts
		if (restart) {
			restartTimer -= delta;
			uiBatch.begin();
			if (restartTimer >= 2f) {
				font.draw(uiBatch, "Ready", Gdx.graphics.getWidth() * 0.36f, Gdx.graphics.getHeight() * 0.54f);
			} else if (restartTimer >= 1.3f) {
				if (!started) {
					subject.notify(null, Event.EVENT_GO);
					started = true;
				}
				font.draw(uiBatch, "Fling!", Gdx.graphics.getWidth() * 0.39f, Gdx.graphics.getHeight() * 0.54f);
			} else {
				started = false;
				restart = false;
				restartTimer = COUTDOWN;
			}
			uiBatch.end();
		}
	}

	/**
	 * Called when the game is reset
	 */
	public void restart() {
		Locator.writeSave();
		game.restart();
		backgroundManager.restart();
		firstJump = secondJump = thirdJump = false;
		restart = true;
		distance = 0;
	}

	/**
	 * Called when screen is resized
	 */
	@Override
	public void resize(int width, int height) {
		float fWidth = width;
		float fHeight = height;
		fWidth *= SCALE;
		fHeight *= SCALE;
		// calculate new viewport
		calcViewport(fWidth, fHeight);
		// sprite scales used for proportions
		setSpriteScales(fWidth / VIRTUAL_WIDTH, fHeight / VIRTUAL_HEIGHT, viewport);

		camera.setToOrtho(false, viewport.width, viewport.height);

		// resize ui
		stage.getViewport().update(width, height, true);
		table.invalidateHierarchy();
		table.setSize(width, height);

		pauseScreen.resize(width, height);
	}

	/**
	 * Bases the resolution off a virtual resolution and crops the screen based
	 * on that
	 * 
	 * @param fWidth
	 * @param fHeight
	 */
	public void calcViewport(float fWidth, float fHeight) {
		float aspectRatio = (float) fWidth / (float) fHeight;
		float scale = 1f;
		Vector2 crop = new Vector2(0f, 0f);
		if (aspectRatio > ASPECT_RATIO) {
			scale = (float) fHeight / (float) VIRTUAL_HEIGHT;
			crop.x = (fWidth - VIRTUAL_WIDTH * scale) / 2f;
		} else if (aspectRatio < ASPECT_RATIO) {
			scale = (float) fWidth / (float) VIRTUAL_WIDTH;
			crop.y = (fHeight - VIRTUAL_HEIGHT * scale) / 2f;
		} else {
			scale = (float) fWidth / (float) VIRTUAL_WIDTH;
		}
		float w = (float) VIRTUAL_WIDTH * scale;
		float h = (float) VIRTUAL_HEIGHT * scale;
		viewport = new Rectangle(crop.x, crop.y, w, h);
	}

	public void setSpriteScales(float x_scale, float y_scale, Rectangle view) {
		// swap values
		Locator.setSpriteScale(y_scale, x_scale, view);
	}

	public TutorialManager getTutorial() {
		return tutorial;
	}

	/**
	 * Called when the game screen appears
	 */
	@Override
	public void show() {
		// reset fade effects
		effects.clear();
		effects.add(new FadeOutTransitionEffect(0.5f));
		effects.add(new FadeInTransitionEffect(0.5f));
		Gdx.input.setInputProcessor(Locator.getInputMultiplexer());
		resumeGame();
	}

	public void increaseDistance(int d) {
		distance += d;
		Locator.setNewDistance(distance);
	}

	/**
	 * Called when game screen goes away
	 */
	@Override
	public void hide() {
		Locator.writeSave();
		Gdx.input.setInputProcessor(null);
		pause();
	}

	@Override
	public void pause() {
		Locator.getAudio().stopMusic();
		Locator.writeSave();
		pause = true;
		pauseScreen.pause();
	}

	@Override
	public void resume() {
	}

	public void resumeGame() {
		pauseScreen.resume();
		Locator.getAudio().playMusic();
		restart = true;
		pause = false;
	}

	@Override
	public void dispose() {

	}

	/**
	 * Set pause button for game screen
	 */
	private void createPauseButton() {
		Image image = new Image(new Texture(Gdx.files.internal("data/art/pause.png")));
		pauseButton = new ImageButton(image.getDrawable());
		pauseButton.addListener(new ClickListener() {
			@Override
			public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
				subject.notify(null, Event.EVENT_CLICK);
				pause();
				return false;
			}
		});
	}

	/**
	 * Creates the coin image and table with the stage for the pause button
	 */
	public void createUI() {
		coinImage = new Image(new Texture(Gdx.files.internal(SINGLE_COIN_PATH)));
		coinImage.setScale(0.75f / UI_SCALE_X, 0.75f / UI_SCALE_Y);
		coinImage.setPosition(Gdx.graphics.getWidth() * 0.21f, Gdx.graphics.getHeight() * 0.965f);

		createPauseButton();
		stage = new Stage();
		table = new Table();
		table.setTransform(true);
		table.setClip(true);
		table.setFillParent(true);
		table.setSize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		table.setPosition(Gdx.graphics.getWidth() * 0.45f, Gdx.graphics.getHeight() * 0.47f);
		table.add(pauseButton).width(64 / UI_SCALE_X).height(64 / UI_SCALE_Y);
		stage.addActor(table);
	}

	/**
	 * This class is an observer so onNotify handles the events
	 */
	@Override
	public void onNotify(Entity entity, Event event) {
		System.out.println(event.name());
		switch (event) {
		case EVENT_FIRST_JUMP:
			firstJump = true;
			break;
		case EVENT_SECOND_JUMP:
			secondJump = true;
			break;
		case EVENT_THIRD_JUMP:
			thirdJump = true;
			break;
		case EVENT_HIT_WALL:
			firstJump = secondJump = thirdJump = false;
			break;
		case EVENT_RESTART:
			if (Locator.difficulty == Locator.HARD) {
				Locator.getDevice().submitLeaderboardScore(distance);
			}
			restart();
			break;
		case EVENT_PASSED_BLOCK:
			increaseDistance(1);
			break;
		case EVENT_UNPASSED_BLOCK:
			increaseDistance(-1);
			break;
		case EVENT_UNPAUSE:
			resumeGame();
			break;
		case EVENT_MENU_TO_GAME:

			break;
		case EVENT_TUTORIAL_COMPLETED:
			Locator.setTutorialCompleted(true);
			break;
		case EVENT_TUTORIAL_DEATH:
			tutorial.reset(subject);
			break;
		case EVENT_COIN_GRABBED:
			Locator.increaseCoins();
			break;
		default:
			break;
		}
	}

	public boolean isPause() {
		return pause;
	}

	public void setPause(boolean pause) {
		this.pause = pause;
	}

	public boolean isRestart() {
		return restart;
	}

	public void setRestart(boolean restart) {
		this.restart = restart;
	}

	public Rectangle getViewport() {
		return viewport;
	}

	public void setViewport(Rectangle viewport) {
		this.viewport = viewport;
	}

	public Subject getSubject() {
		return subject;
	}

	public Box2DDebugRenderer getRenderer() {
		return renderer;
	}

}
